package com.gjy.design.strategy.compression;

import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;

/**
 * GZIP压缩策略的具体实现。
 * 注意：GZIP是一种流式压缩格式，不原生支持归档多个文件。
 * 此实现约定：
 * 1. compress() 方法只处理输入数组中的第一个源文件。
 * 2. 如果源是文件夹，则抛出 UnsupportedOperationException。
 *
 * @author gjy
 * @version 1.0
 * @since 2025-11-06 14:43:43
 */
public class GzipCompressionStrategy implements CompressionStrategy {
    private static final Logger log = LoggerFactory.getLogger(GzipCompressionStrategy.class);

    @Override
    public void compress(File[] source, File target) throws IOException {
        // 输入校验
        if (source == null || source.length == 0) {
            throw new IllegalArgumentException("源文件/文件夹数组不能为空");
        }
        if (source.length > 1) {
            System.err.println("警告: GZIP格式不支持压缩多个文件到单个归档。将只处理第一个源文件: " + source[0].getAbsolutePath());
        }

        File fileToCompress = source[0];
        if (fileToCompress.isDirectory()) {
            throw new UnsupportedOperationException("GZIP不支持压缩文件夹。");
        }

        try (OutputStream fos = Files.newOutputStream(target.toPath());
             GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos);
             InputStream fis = Files.newInputStream(fileToCompress.toPath())) {
            byte[] buffer = new byte[8192];
            int len;
            while ((len = fis.read(buffer)) > 0) {
                gzos.write(buffer, 0, len);
            }
        }
    }

    @Override
    public void decompress(File source, File target) throws IOException {
        // 输入校验
        if (!source.isFile()) {
            throw new IllegalArgumentException("源文件必须是文件，而不是目录");
        }
        if (target.exists() && target.isDirectory()) {
            // 如果目标是目录，将解压后的文件放在该目录下，文件名自动推导
            File decompressedFile = new File(target, getDecompressedFileName(source.getName()));
            decompressToFile(source, decompressedFile);
        } else if (target.exists() || !target.isDirectory()) {
            // 如果目标是文件或不存在的文件路径，直接解压到该文件
            decompressToFile(source, target);
        } else {
            throw new IOException("目标路径无效，已存在同名目录: " + target.getAbsolutePath());
        }
    }

    /**
     * 执行实际的解压操作到指定文件。
     *
     * @param source 压缩源文件
     * @param target 解压目标文件
     * @throws IOException IO异常
     */
    private void decompressToFile(File source, File target) throws IOException {
        // 确保目标文件的父目录存在
        File parent = target.getParentFile();
        if (parent != null && !parent.exists()) {
            boolean b = parent.mkdirs();
            log.warn("解压创建目录: {}", b);
        }

        try (InputStream fis = Files.newInputStream(source.toPath());
             GzipCompressorInputStream gzis = new GzipCompressorInputStream(fis);
             OutputStream fos = Files.newOutputStream(target.toPath())) {
            byte[] buffer = new byte[8192];
            int len;
            while ((len = gzis.read(buffer)) > 0) {
                fos.write(buffer, 0, len);
            }
        }
    }

    /**
     * 从.gz文件名推导出解压后的文件名。
     *
     * @param gzFileName .gz文件名
     * @return 解压后的文件名
     */
    private String getDecompressedFileName(String gzFileName) {
        if (gzFileName.toLowerCase().endsWith(".gz")) {
            return gzFileName.substring(0, gzFileName.length() - 3);
        }
        return gzFileName + ".decompressed";
    }
}
